package cn.sciento.boot.data.change.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.util.SelectUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import cn.sciento.boot.data.change.dto.InsertType;
import cn.sciento.boot.data.change.dto.MybatisInvocation;

public class JsqlParserUtil {
  private static final Integer THRESHOLD_VALUE = Integer.valueOf(1000);
  
  private static final String INSERT = "insert";
  
  private static final String SELECT = "select";
  
  private static final String FROM = "from";
  
  private static final String DEFAULT_PLACE_HOLDER = "\\?";
  
  private static final String ADDITIONAL_PARAMETERS = "additionalParameters";
  
  public static List<String> getWhereColumn(Expression whereExpression) {
    final List<String> result = new ArrayList<>();
    if (null == whereExpression)
      return result; 
    whereExpression.accept((ExpressionVisitor)new ExpressionVisitorAdapter() {
          public void visit(Column expr) {
            if (!result.contains(expr.getColumnName()))
              result.add(expr.getColumnName().toLowerCase()); 
          }
        });
    return result;
  }
  
  public static Integer getWhereColumnMappingCount(Expression whereExpression) {
    if (null == whereExpression)
      return Integer.valueOf(0); 
    String whereStr = whereExpression.toString();
    return Integer.valueOf(whereStr.length() - whereStr.replaceAll("\\?", "").length());
  }
  
  public static Select getSelect(Table table, List<Column> column, Expression whereExpression) {
    Column[] selectColumns = column.<Column>toArray(new Column[column.size()]);
    Select select = SelectUtils.buildSelectFromTableAndExpressions(table, (Expression[])selectColumns);
    PlainSelect selectPlain = (PlainSelect)select.getSelectBody();
    selectPlain.setWhere(whereExpression);
    return select;
  }
  
  public static String buildWhereExpression(List<Object> keyValues, String column) {
    StringBuilder sqlBuilder = (new StringBuilder(" where ")).append(column).append(" IN ");
    int size = keyValues.size();
    if (size < THRESHOLD_VALUE.intValue()) {
      sqlBuilder.append(buildInSql(keyValues));
    } else {
      int batchCount = size / THRESHOLD_VALUE.intValue();
      int remain = size % THRESHOLD_VALUE.intValue();
      for (int i = 0; i <= batchCount; i++) {
        List<Object> tempKeyValues;
        if (i != batchCount) {
          tempKeyValues = keyValues.subList(i * THRESHOLD_VALUE.intValue(), (i + 1) * THRESHOLD_VALUE.intValue());
        } else {
          tempKeyValues = keyValues.subList(i * THRESHOLD_VALUE.intValue(), i * THRESHOLD_VALUE.intValue() + remain);
        } 
        if (i == 0) {
          sqlBuilder.append(buildInSql(tempKeyValues));
        } else {
          sqlBuilder.append(" OR " + column + " IN ").append(buildInSql(tempKeyValues));
        } 
      } 
    } 
    return sqlBuilder.toString();
  }
  
  public static String buildSelectSql(String tableName, String whereExpression) {
    StringBuilder sqlBuilder = (new StringBuilder("SELECT * FROM ")).append(tableName).append(whereExpression);
    return sqlBuilder.toString();
  }
  
  private static String buildInSql(List<Object> keyValues) {
    StringBuilder sqlBuilder = (new StringBuilder()).append(keyValues);
    String inSql = sqlBuilder.toString();
    inSql = inSql.replace("[", "(");
    inSql = inSql.replace("]", ")");
    return inSql;
  }
  
  public static InsertType getInsertType(Insert insertStatement) {
    if (insertStatement.getSelect() != null)
      return InsertType.BATCH_BY_SELECT; 
    if (insertStatement.getItemsList() != null && insertStatement
      .getItemsList() instanceof net.sf.jsqlparser.expression.operators.relational.MultiExpressionList)
      return InsertType.BATCH_BY_VALUES; 
    return InsertType.SINGLE;
  }
  
  public static BoundSql buildSelectBoundSql(MybatisInvocation mybatisInvocation, List<ParameterMapping> newParamMap, String selectSql) {
    Configuration configuration = mybatisInvocation.getMappedStatement().getConfiguration();
    List<ParameterMapping> parameterMappings = newParamMap;
    Object parameter = mybatisInvocation.getParameter();
    BoundSql boundSql = mybatisInvocation.getBoundSql();
    BoundSql queryBoundSql = new BoundSql(configuration, selectSql, parameterMappings, parameter);
    Object additionalParameters = FieldUtil.getField(boundSql, "additionalParameters");
    if (additionalParameters != null) {
      HashMap<String, Object> additionalParametersMap = (HashMap<String, Object>)additionalParameters;
      for (Map.Entry<String, Object> entry : additionalParametersMap.entrySet())
        queryBoundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); 
    } 
    return queryBoundSql;
  }
}
